#include <pspkernel.h>
#include <pspdebug.h>
#include <pspsdk.h>
#include <pspdisplay.h>
#include <pspwlan.h>
#include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_adhoc.h>
#include <pspnet_adhocctl.h>
#include <pspnet_adhocmatching.h>
#include <psptypes.h>
#include <psputility.h>

#include <string.h>
#include "adhoc.h"

#define printf pspDebugScreenPrintf

#define TW_STAGE_NET_INIT 					1
#define TW_STAGE_NET_ADHOC_INIT 			2
#define TW_STAGE_NET_ADHOC_CTL_INIT 		3
#define TW_STAGE_NET_ADHOC_CTL_CONNECT 		4
#define TW_STAGE_NET_ADHOC_PDP_CREATE		5
#define TW_STAGE_NET_ADHOC_MATCHING_INIT	6
#define TW_STAGE_NET_ADHOC_MATCHING_CREATE	7	
#define TW_STAGE_NET_ADHOC_MATCHING_START	8

#define TW_DISCONNECTED	0
#define TW_CONNECTED	1
#define TW_ACCEPTED		2
#define TW_REJECTED		3
#define TW_ESTABLISHED	4
#define TW_JOINED		5
#define TW_CANCELLED	6
#define TW_SELECTED		7


char* macToChar(macadr mac)
{
	static char ret[6];
	
	ret[0] = mac.byte1;
	ret[1] = mac.byte2;
	ret[2] = mac.byte3;
	ret[3] = mac.byte4;
	ret[4] = mac.byte5;
	ret[5] = mac.byte6;

	return ret;
}

/* twAdhocPSP */

twAdhocPSP::twAdhocPSP(string name, macadr mac)
{
	m_name = name;
	m_mac = mac;
	m_state = TW_DISCONNECTED;
}

char* twAdhocPSP::macToString(macadr mac)
{
	static char ret[20];
	
	sprintf(ret, "%02X:%02X:%02X:%02X:%02X:%02X", mac.byte1, mac.byte2, mac.byte3, mac.byte4, mac.byte5, mac.byte6);

	return ret;
}

char* twAdhocPSP::getMacStr() { return macToString(m_mac); }

macadr twAdhocPSP::getMac() { return m_mac; }

string twAdhocPSP::getName() { return m_name; }

int twAdhocPSP::getState() { return m_state; }

void twAdhocPSP::setState(unsigned int state)
{
	m_state = state;
}

/* twAdhocPSPMgr */

bool twAdhocPSPMgr::compareMac(macadr mac1, macadr mac2)
{
	if( mac1.byte1 == mac2.byte1 &&
		mac1.byte2 == mac2.byte2 &&
		mac1.byte3 == mac2.byte3 &&
		mac1.byte4 == mac2.byte4 &&
		mac1.byte5 == mac2.byte5 &&
		mac1.byte6 == mac2.byte6)
		return true;
	else
		return false;
}

bool twAdhocPSPMgr::addPSP(macadr mac, string name)
{
	int i;
	for(i = 0; i < (int)m_vecPSP.size(); i++)
	{
		if(m_vecPSP[i].getName() == name || compareMac(m_vecPSP[i].getMac(), mac))
			return false;
	}

	twAdhocPSP tempPSP(name, mac);
	m_vecPSP.push_back(tempPSP);
	return true;
}

twAdhocPSP* twAdhocPSPMgr::getPSP(string name)
{
	int i;
	for(i = 0; i < (int)m_vecPSP.size(); i++)
	{
		if(m_vecPSP[i].getName() == name)
			return &m_vecPSP[i];
	}
	return NULL;
}

twAdhocPSP* twAdhocPSPMgr::getPSP(macadr mac)
{
	int i;
	for(i = 0; i < (int)m_vecPSP.size(); i++)
	{
		if(compareMac(m_vecPSP[i].getMac(), mac))
			return &m_vecPSP[i];
	}
	return NULL;
}


void twAdhocPSPMgr::removePSP(string name)
{
	int i;
	for(i = 0; i < (int)m_vecPSP.size(); i++)
	{
		if(m_vecPSP[i].getName() == name)
			m_vecPSP.erase(m_vecPSP.begin()+i, m_vecPSP.begin()+i+1);
	}
}

void twAdhocPSPMgr::removePSP(macadr mac)
{
	int i;
	for(i = 0; i < (int)m_vecPSP.size(); i++)
	{
		if(compareMac(m_vecPSP[i].getMac(), mac))
			m_vecPSP.erase(m_vecPSP.begin()+i, m_vecPSP.begin()+i+1);
	}
}

void twAdhocPSPMgr::deleteAll()
{
	m_vecPSP.empty();
}

/* twAdhocCallback */

void twAdhocCallback(int unk1, int event, unsigned char *mac2, int optLen, void *optData)
{
	macadr tempmac = { mac2[0], mac2[1], mac2[2], mac2[3], mac2[4], mac2[5] };

	twAdhocPSP* pAdhocPSP = twAdhocPSPMgr::getInstance().getPSP(tempmac);

	switch(event) {
	case MATCHING_JOINED:
		if(pAdhocPSP == NULL)
		{
			char* buff;
			buff = new char[optLen];
			memcpy(buff, optData, optLen);

			twAdhocPSPMgr::getInstance().addPSP(tempmac, buff);
			twAdhocPSPMgr::getInstance().getPSP(tempmac)->setState(TW_JOINED);
			printf("\nFound PSP: %s MAC: %s", twAdhocPSPMgr::getInstance().getPSP(tempmac)->getName().c_str(), twAdhocPSPMgr::getInstance().getPSP(tempmac)->getMacStr());
			delete[] buff;
		}
		break;
	case MATCHING_DISCONNECT:
		if(pAdhocPSP != NULL)
		{
			printf("\nLost PSP: %s MAC: %s", twAdhocPSPMgr::getInstance().getPSP(tempmac)->getName().c_str(), twAdhocPSPMgr::getInstance().getPSP(tempmac)->getMacStr());
			twAdhocPSPMgr::getInstance().removePSP(tempmac);
		}
		break;
	case MATCHING_SELECTED:
		if(pAdhocPSP != NULL)
		{
			twAdhocPSPMgr::getInstance().getPSP(tempmac)->setState(TW_SELECTED);
		}
		break;
	case MATCHING_REJECTED:
		if(pAdhocPSP != NULL)
		{
			twAdhocPSPMgr::getInstance().getPSP(tempmac)->setState(TW_REJECTED);
		}
		break;
	case MATCHING_ESTABLISHED:
		if(pAdhocPSP != NULL)
		{
			twAdhocPSPMgr::getInstance().getPSP(tempmac)->setState(TW_ESTABLISHED);
		}
		break;
	default:
		break;
	}
}


// modified from Adhoc examples

bool twAdhoc::init()
{	
	unsigned char mac[6];
	struct productStruct product;
	mac[0] = 0;
	
	strcpy(product.product, "ULUS99999");
	product.unknown = 0;
	
	unsigned int err;
	
	err = sceNetInit(0x20000, 0x20, 0x1000, 0x20, 0x1000);
	if (err != 0)
	    return false;
	loadingstage = TW_STAGE_NET_INIT;
	
	err = sceNetAdhocInit();
	if (err != 0)
	    return false;
	loadingstage = TW_STAGE_NET_ADHOC_INIT;
	
	err = sceNetAdhocctlInit(0x2000, 0x20, &product);
	if (err != 0)
	    return false;
	loadingstage = TW_STAGE_NET_ADHOC_CTL_INIT;
	
	char voidstr [] = "";
	err = sceNetAdhocctlConnect(voidstr);
	if (err != 0)
	    return false;
	loadingstage = TW_STAGE_NET_ADHOC_CTL_CONNECT;
	
	while (1)
	{
	    int state;
	    err = sceNetAdhocctlGetState(&state);
	    if (err != 0)
			return false;
	
	    if (state == 1)
	        break;  // connected
	
	    // wait a little before polling again
	    sceKernelDelayThread(50*1000); // 50ms
	}
	
	sceWlanGetEtherAddr(mac);
	
	m_pdpId = sceNetAdhocPdpCreate(mac,
		     0x309,		// 0x309 in lumines
		     0x400, 	// 0x400 in lumines
		     0);		// 0 in lumines
	if(m_pdpId <= 0)
		return false;

	loadingstage = TW_STAGE_NET_ADHOC_PDP_CREATE;
	
	sceNetAdhocMatchingInit(0x20000);

	loadingstage = TW_STAGE_NET_ADHOC_MATCHING_INIT;
	
	m_matchingId = sceNetAdhocMatchingCreate( 3,
											0xa,
											0x22b,
											0x800,
											0x2dc6c0,
											0x5b8d80,
											3,
											0x7a120,
											twAdhocCallback);

	loadingstage = TW_STAGE_NET_ADHOC_MATCHING_CREATE;
	
	
	char nick[40];
	
	sceUtilityGetSystemParamString(PSP_SYSTEMPARAM_ID_STRING_NICKNAME, nick, 40);
	
	err = sceNetAdhocMatchingStart(m_matchingId, 	// 1 in lumines (presuming what is returned from create)
			 0x10,									// 0x10
			 0x2000,								// 0x2000
			 0x10,									// 0x10
			 0x2000,								// 0x2000
			 40,
			 nick);
	
	loadingstage = TW_STAGE_NET_ADHOC_MATCHING_START;
	
	return true;
}

bool twAdhoc::unInit()
{
	if(loadingstage >= TW_STAGE_NET_ADHOC_CTL_CONNECT)
		sceNetAdhocctlDisconnect();

	if(loadingstage >= TW_STAGE_NET_ADHOC_PDP_CREATE)
		sceNetAdhocPdpDelete(m_pdpId,0);

	if(loadingstage >= TW_STAGE_NET_ADHOC_MATCHING_START)
		sceNetAdhocMatchingStop(m_matchingId);

	if(loadingstage >= TW_STAGE_NET_ADHOC_MATCHING_CREATE)
		sceNetAdhocMatchingDelete(m_matchingId);

	if(loadingstage >= TW_STAGE_NET_ADHOC_MATCHING_INIT)
		sceNetAdhocMatchingTerm();
	
	if(loadingstage >= TW_STAGE_NET_ADHOC_CTL_INIT)
		sceNetAdhocctlTerm();

	if(loadingstage >= TW_STAGE_NET_ADHOC_INIT)
		sceNetAdhocTerm();

	if(loadingstage >= TW_STAGE_NET_INIT)
		sceNetTerm();

	loadingstage = 0;

    return true;
}

bool twAdhoc::loadMods()
{
	SceUID modId = pspSdkLoadStartModule("flash0:/kd/ifhandle.prx", PSP_MEMORY_PARTITION_KERNEL);
	if(!modId) return false;
	modId = pspSdkLoadStartModule("flash0:/kd/memab.prx", PSP_MEMORY_PARTITION_KERNEL);
	if(!modId) return false;
	modId = pspSdkLoadStartModule("flash0:/kd/pspnet_adhoc_auth.prx", PSP_MEMORY_PARTITION_KERNEL);
	if(!modId) return false;
	modId = pspSdkLoadStartModule("flash0:/kd/pspnet.prx", PSP_MEMORY_PARTITION_USER);
	if(!modId) return false;
	modId = pspSdkLoadStartModule("flash0:/kd/pspnet_adhoc.prx", PSP_MEMORY_PARTITION_USER);
	if(!modId) return false;
	modId = pspSdkLoadStartModule("flash0:/kd/pspnet_adhocctl.prx", PSP_MEMORY_PARTITION_USER);
	if(!modId) return false;
	modId = pspSdkLoadStartModule("flash0:/kd/pspnet_adhoc_matching.prx", PSP_MEMORY_PARTITION_USER);
	if(!modId) return false;
	
	return true;
}